/* vi:set ts=4 sts=4 sw=4 foldmethod=marker:
 *
 * VIM - Vi IMproved        by Bram Moolenaar
 *              GUI/Motif support by Robert Webb
 *              Macintosh port by Dany St-Amant
 *                        and Axel Kielhorn
 *              Port to MPW by Bernhard Pruemmer
 *              Initial Carbon port by Ammon Skidmore
 *              Initial Cocoa port by Jjgod Jiang
 *
 * Do ":help uganda"  in Vim to read copying and usage conditions.
 * Do ":help credits" in Vim to see a list of people who contributed.
 * See README.txt for an overview of the Vim source code.
 */

#include "vim.h"
#import <Cocoa/Cocoa.h>

/* Internal Data Structures {{{ */
    
enum drawing_request_type {
    REQ_NONE, 
    REQ_CLEAR_ALL,
    REQ_CLEAR_BLOCK, 
    REQ_INVERT_RECT, 
    REQ_FLASH, 
    REQ_DRAW_STRING, 
    REQ_DELETE_LINES, 
    REQ_INSERT_LINES
};

typedef struct drawing_request {
    int type;

    union {
        /* REQ_DRAW_STRING */
        struct {
            int row;
            int col;
            char_u *s;
            int len;
            int flag;
        } str;

        /* REQ_CLEAR_BLOCK */
        struct {
            int row1;
            int col1;
            int row2;
            int col2;
            guicolor_T color;
        } block;
        
        /* REQ_INVERT_RECT */
        struct {
            int row;
            int col;
            int row_num;
            int col_num;
        } rect;
        
        /* REQ_INSERT_LINES, REQ_DELETE_LINES */
        struct {
            int row;
            int num; 
        } lines;
        
        guicolor_T color;
    } u;
} drawing_request;

#define MAX_DRAWING_REQUESTS    4096

/* the head of drawing request queue */
drawing_request  req_pool[MAX_DRAWING_REQUESTS];
int              req_index = 0;

/* Key mapping {{{2 */

static struct
{
    UniChar function_key;
    int     vim_key;
} function_key_mapping[] =
{
    { NSUpArrowFunctionKey,     K_UP    },
    { NSDownArrowFunctionKey,   K_DOWN  },
    { NSLeftArrowFunctionKey,   K_LEFT  },
    { NSRightArrowFunctionKey,  K_RIGHT },

    { NSF1FunctionKey,          K_F1    },
    { NSF2FunctionKey,          K_F2    },
    { NSF3FunctionKey,          K_F3    },
    { NSF4FunctionKey,          K_F4    },
    { NSF5FunctionKey,          K_F5    },
    { NSF6FunctionKey,          K_F6    },
    { NSF7FunctionKey,          K_F7    },
    { NSF8FunctionKey,          K_F8    },
    { NSF9FunctionKey,          K_F9    },
    { NSF10FunctionKey,         K_F10   },

    { NSF11FunctionKey,         K_F11   },
    { NSF12FunctionKey,         K_F12   },
    { NSF13FunctionKey,         K_F13   },
    { NSF14FunctionKey,         K_F14   },
    { NSF15FunctionKey,         K_F15   },

    { NSInsertFunctionKey,      K_INS   },
    { NSDeleteFunctionKey,      K_DEL   },
    { NSHomeFunctionKey,        K_HOME  },
    { NSEndFunctionKey,         K_END   },

    { NSPageUpFunctionKey,      K_PAGEUP    },
    { NSPageDownFunctionKey,    K_PAGEDOWN  },

    /* End of list marker: */
    { 0, 0 }, 
};

/* 2}}} */

#define VIM_MAX_ROWS_ON_SCREEN  1024
    
@interface VimTextView: NSView <NSTextInput>
@end

@interface VimAppDelegate: NSObject
- (void) oneShotTimer:(NSTimer *)timer;
@end

guicolor_T fg_color, bg_color, sp_color;
char       row_to_redraw[VIM_MAX_ROWS_ON_SCREEN];
NSView    *content_view;
int        app_is_running = FALSE;
VimAppDelegate *app_delegate;

/* Data Structures }}}*/

/* Internal functions prototypes {{{ */

@interface NSApplication (VimAdditions)
-(void) setAppleMenu:(NSMenu *)aMenu;
@end

int hex_digit(int c);
NSColor *NSColorFromGuiColor(guicolor_T color);

int create_window(int width, int height);
void set_application_menu();

void dirty_all_rows();
void dirty_row(int row);
void clean_all_rows();
void clean_row(int row);
void redraw();

unsigned int modifiers_to_vim(unsigned int mac_modifiers);
int function_key_to_vim(UniChar key_char, unsigned int vim_modifiers);

void request_append(drawing_request *req);
void request_clean_up();
void request_draw_string(int row, int col, char_u *s, int len, int flag);
void request_clear_all(guicolor_T color);
void request_flash();
void request_clear_block(int row1, int col1, int row2, int col2, guicolor_T color);
void request_invert_rect(int row, int col, int row_num, int col_num);
void request_insert_lines(int row, int num_lines);
void request_delete_lines(int row, int num_lines);

/* Internal functions prototypes }}} */

/* Initializtion and Finalization {{{ */

int gui_mch_init()
{
    fprintf(stderr, "gui_mch_init\n");

    gui.char_width = 7;
    gui.char_height = 11;
    gui.char_ascent = 6;
    gui.num_rows = 24;
    gui.num_cols = 80;
    gui.in_focus = TRUE;

    gui.norm_pixel = 0x00000000;
    gui.back_pixel = 0x00FFFFFF;
    set_normal_colors();

    gui_check_colors();
    gui.def_norm_pixel = gui.norm_pixel;
    gui.def_back_pixel = gui.back_pixel;

    /* Get the colors for the highlight groups (gui_check_colors() might have
     * changed them) */
    highlight_gui_started();

#ifdef FEAT_MENU
    gui.menu_height = 0;
#endif
    gui.scrollbar_height = gui.scrollbar_width = 15;
    gui.border_offset = gui.border_width = 2;

    return OK;
}

int gui_mch_init_check()
{
    fprintf(stderr, "gui_mch_init_check\n");

    /* see main.c for reason to disallow */
    if (disallow_gui) 
        return FAIL;

    return OK;
}

void gui_mch_exit(int rc)
{
    fprintf(stderr, "gui_mch_exit\n");

    exit(rc);
}

int gui_mch_open()
{
    fprintf(stderr, "gui_mch_open\n");

    if (gui_win_x != -1 && gui_win_y != -1)
        gui_mch_set_winpos(gui_win_x, gui_win_y);

    return OK;
}

void gui_mch_prepare(int *argc, char **argv)
{
    NSAutoreleasePool *pool;

    fprintf(stderr, "gui_mch_prepare\n");

    pool = [NSAutoreleasePool new];

    [NSApplication sharedApplication];
    
    app_delegate = [VimAppDelegate new];
    [NSApp setDelegate: app_delegate];

    [NSApp setMainMenu: [[NSMenu new] autorelease]];
    set_application_menu();

    [pool release];
}

void gui_mch_set_shellsize(
    int width,
    int height,
    int min_width,
    int min_height,
    int base_width,
    int base_height,
    int direction)
{
    fprintf(stderr, "gui_mch_set_shellsize: "
            "(%d, %d, %d, %d, %d, %d, %d)\n", 
            width, height, min_width, min_height, 
            base_width, base_height, direction);

    gui_resize_shell(width, height);
}

void gui_mch_set_text_area_pos(int x, int y, int w, int h)
{
    fprintf(stderr, "gui_mch_set_text_area_pos: "
            "%d, %d, %d, %d\n", x, y, w, h);
}

/* Initializtion and Finalization }}} */

/* Event related {{{ */

void gui_mch_update()
{
    gui_mch_wait_for_chars(0);
}

/* wtime < 0: wait forever
 * wtime > 0: wait wtime milliseconds
 * wtime = 0: don't wait, only poll existing events
 */
int gui_mch_wait_for_chars(int wtime)
{
    NSEvent *event;
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
    NSDate  *date;
    static   int first_time = 1;
    
    if (wtime == 0)
    {
        NSLog(@"gui_mch_wait_for_chars: don't wait");
        date = [NSDate distantPast];
    } 
    else if (wtime > 0)
    {
        NSLog(@"gui_mch_wait_for_chars: wait for %d ms", wtime);
        date = [NSDate dateWithTimeIntervalSinceNow: (double) wtime / 1000.0];
    }
    /* wtime < 0, wait forever */
    else
    {
        NSLog(@"gui_mch_wait_for_chars: wait forever");
        date = [NSDate distantFuture];
    }
    
    /* It's tricky here: we don't want to use -[NSApplication run:] 
     * all the time, but we need it to do some initialization for
     * the first time this app starts. So we install a timer to 
     * stop: NSApp just after it runs.
     */
    if (first_time)
    {
        NSLog(@"first time, begin initialization...");
        first_time = 0;
        [NSTimer scheduledTimerWithTimeInterval: 0.1
                                         target: app_delegate
                                       selector: @selector(oneShotTimer:)
                                       userInfo: 0
                                        repeats: NO];
        [NSApp run];
        NSLog(@"end initialization.");
    }

    app_is_running = TRUE;
    while (app_is_running == TRUE && 
           (event = [NSApp nextEventMatchingMask: NSAnyEventMask
                                       untilDate: date
    					                  inMode: NSDefaultRunLoopMode
    					                 dequeue: YES]) != nil)
    {
	    [NSApp sendEvent: event];
    }
	    
    [pool release];
    return OK;
}

/* Event related }}} */

/* Input Method Handling {{{ */

int im_get_status()
{
    if (! gui.in_use)
            return 0;

    return 0;
}

void im_set_active(int active)
{
}

void im_set_position(int row, int col)
{
}

/* Input Method Handling }}} */

/* Misc Stuff {{{ */

void gui_mch_get_screen_dimensions(int *screen_w, int *screen_h)
{
    CGRect rect;

    rect = CGDisplayBounds(CGMainDisplayID());

    *screen_w = (int) rect.size.width;
    *screen_h = (int) rect.size.height;

    fprintf(stderr, "gui_mch_get_screen_dimensions: %d, %d\n", 
            *screen_w, *screen_h);
}

#ifdef USE_MCH_ERRMSG

void display_errors()
{
    fprintf(stderr, "display_errors\n");
    fflush(stderr);
}

#endif

int gui_mch_haskey(char_u *name)
{
    return OK;
}

void gui_mch_beep()
{
}

void gui_mch_toggle_tearoffs(int enable)
{
}

/* Misc Stuff }}} */

/* Font Handling {{{ */

int gui_mch_init_font(char_u *font_name, int fontset)
{
    fprintf(stderr, "gui_mch_init_font: %s, %d\n", 
            font_name, fontset);
    return OK;
}

void gui_mch_free_font(GuiFont font)
{
}

void gui_mch_set_font(GuiFont font)
{
    fprintf(stderr, "gui_mch_set_font\n");
}

GuiFont gui_mch_get_font(char_u *name, int giveErrorIfMissing)
{
    fprintf(stderr, "gui_mch_get_font: %s\n", name);

    return NOFONT;
}

char_u *gui_mch_get_fontname(GuiFont font, char_u *name)
{
    if (name == NULL)
        return NULL;
    
    return vim_strsave(name);
}

int gui_mch_adjust_charheight()
{
    return OK;
}

/* Font Handling }}} */

/* Window Handling {{{ */

void gui_mch_set_foreground()
{
    fprintf(stderr, "gui_mch_set_foreground\n");
}

void gui_mch_set_winpos(int x, int y)
{
    fprintf(stderr, "gui_mch_set_winpos: %d, %d\n", x, y);
}

int gui_mch_get_winpos(int *x, int *y)
{
    return FAIL;
}

void gui_mch_settitle(char_u *title, char_u *icon)
{
    fprintf(stderr, "gui_mch_set_title: (%s, %s)\n", title, icon);
}

void gui_mch_iconify()
{
    fprintf(stderr, "gui_mch_iconify\n");
}

/* Window Handling }}} */

/* Menu Handling {{{ */

/* TODO: 
 *
 * 1. popup menus are ignored. 
 * 2. command ID has not been assigned.
 */

void gui_mch_add_menu(vimmenu_T *menu, int idx)
{
    // fprintf(stderr, "gui_mch_add_menu\n");
}

#define KEY_GLYPH_TABLE_LEN (sizeof(mac_key_glyph_table) / sizeof(mac_key_glyph_table[0]))

void gui_mch_add_menu_item(vimmenu_T *menu, int idx)
{
}

void gui_mch_destroy_menu(vimmenu_T *menu)
{
}

void gui_mch_draw_menubar()
{
    fprintf(stderr, "gui_mch_draw_menubar\n");
}

void gui_mch_menu_grey(vimmenu_T *menu, int grey)
{
    
}

void gui_mch_menu_hidden(vimmenu_T *menu, int hidden)
{
    /* There's no hidden mode on MacOS */
    gui_mch_menu_grey(menu, hidden);
}

void gui_mch_show_popupmenu(vimmenu_T *menu)
{
}

void gui_mch_enable_menu(int flag)
{
    /* menu is always active */
}

void gui_mch_set_menu_pos(int x, int y, int w, int h)
{
    /* menu position is fixed, always at the top */
}

/* Menu Handling }}} */

/* Dialog related {{{ */

char_u *gui_mch_browse(
    int saving,
    char_u *title,
    char_u *dflt,
    char_u *ext,
    char_u *initdir,
    char_u *filter)
{
    return NULL;
}

int gui_mch_dialog(
    int     type,
    char_u  *title,
    char_u  *message,
    char_u  *buttons,
    int     dfltbutton,
    char_u  *textfield)
{
    return 1;
}

/* Dialog related }}} */

/* Color related {{{ */

/* Colors Macros */
#define RGB(r,g,b)  ((r) << 16) + ((g) << 8) + (b)
#define Red(c)      ((c & 0x00FF0000) >> 16)
#define Green(c)    ((c & 0x0000FF00) >>  8)
#define Blue(c)     ((c & 0x000000FF) >>  0)

long_u gui_mch_get_rgb(guicolor_T pixel)
{
    return 0;
}

void gui_mch_new_colors()
{
}

guicolor_T gui_mch_get_color(char_u *name)
{
    typedef struct guicolor_tTable
    {
        char        *name;
        guicolor_T  color;
    } guicolor_tTable;

    /*
     * The comment at the end of each line is the source
     * (Mac, Window, Unix) and the number is the unix rgb.txt value
     */
    static guicolor_tTable table[] =
    {
    { "Black",      RGB(0x00, 0x00, 0x00) },
    { "darkgray",   RGB(0x80, 0x80, 0x80) }, /*W*/
    { "darkgrey",   RGB(0x80, 0x80, 0x80) }, /*W*/
    { "Gray",       RGB(0xC0, 0xC0, 0xC0) }, /*W*/
    { "Grey",       RGB(0xC0, 0xC0, 0xC0) }, /*W*/
    { "lightgray",  RGB(0xE0, 0xE0, 0xE0) }, /*W*/
    { "lightgrey",  RGB(0xE0, 0xE0, 0xE0) }, /*W*/
    { "gray10",     RGB(0x1A, 0x1A, 0x1A) }, /*W*/
    { "grey10",     RGB(0x1A, 0x1A, 0x1A) }, /*W*/
    { "gray20",     RGB(0x33, 0x33, 0x33) }, /*W*/
    { "grey20",     RGB(0x33, 0x33, 0x33) }, /*W*/
    { "gray30",     RGB(0x4D, 0x4D, 0x4D) }, /*W*/
    { "grey30",     RGB(0x4D, 0x4D, 0x4D) }, /*W*/
    { "gray40",     RGB(0x66, 0x66, 0x66) }, /*W*/
    { "grey40",     RGB(0x66, 0x66, 0x66) }, /*W*/
    { "gray50",     RGB(0x7F, 0x7F, 0x7F) }, /*W*/
    { "grey50",     RGB(0x7F, 0x7F, 0x7F) }, /*W*/
    { "gray60",     RGB(0x99, 0x99, 0x99) }, /*W*/
    { "grey60",     RGB(0x99, 0x99, 0x99) }, /*W*/
    { "gray70",     RGB(0xB3, 0xB3, 0xB3) }, /*W*/
    { "grey70",     RGB(0xB3, 0xB3, 0xB3) }, /*W*/
    { "gray80",     RGB(0xCC, 0xCC, 0xCC) }, /*W*/
    { "grey80",     RGB(0xCC, 0xCC, 0xCC) }, /*W*/
    { "gray90",     RGB(0xE5, 0xE5, 0xE5) }, /*W*/
    { "grey90",     RGB(0xE5, 0xE5, 0xE5) }, /*W*/
    { "white",      RGB(0xFF, 0xFF, 0xFF) },
    { "darkred",    RGB(0x80, 0x00, 0x00) }, /*W*/
    { "red",        RGB(0xDD, 0x08, 0x06) }, /*M*/
    { "lightred",   RGB(0xFF, 0xA0, 0xA0) }, /*W*/
    { "DarkBlue",   RGB(0x00, 0x00, 0x80) }, /*W*/
    { "Blue",       RGB(0x00, 0x00, 0xD4) }, /*M*/
    { "lightblue",  RGB(0xA0, 0xA0, 0xFF) }, /*W*/
    { "DarkGreen",  RGB(0x00, 0x80, 0x00) }, /*W*/
    { "Green",      RGB(0x00, 0x64, 0x11) }, /*M*/
    { "lightgreen", RGB(0xA0, 0xFF, 0xA0) }, /*W*/
    { "DarkCyan",   RGB(0x00, 0x80, 0x80) }, /*W ?0x307D7E */
    { "cyan",       RGB(0x02, 0xAB, 0xEA) }, /*M*/
    { "lightcyan",  RGB(0xA0, 0xFF, 0xFF) }, /*W*/
    { "darkmagenta",RGB(0x80, 0x00, 0x80) }, /*W*/
    { "magenta",    RGB(0xF2, 0x08, 0x84) }, /*M*/
    { "lightmagenta",RGB(0xF0, 0xA0, 0xF0) }, /*W*/
    { "brown",      RGB(0x80, 0x40, 0x40) }, /*W*/
    { "yellow",     RGB(0xFC, 0xF3, 0x05) }, /*M*/
    { "lightyellow",RGB(0xFF, 0xFF, 0xA0) }, /*M*/
    { "darkyellow", RGB(0xBB, 0xBB, 0x00) }, /*U*/
    { "SeaGreen",   RGB(0x2E, 0x8B, 0x57) }, /*W 0x4E8975 */
    { "orange",     RGB(0xFC, 0x80, 0x00) }, /*W 0xF87A17 */
    { "Purple",     RGB(0xA0, 0x20, 0xF0) }, /*W 0x8e35e5 */
    { "SlateBlue",  RGB(0x6A, 0x5A, 0xCD) }, /*W 0x737CA1 */
    { "Violet",     RGB(0x8D, 0x38, 0xC9) }, /*U*/
    };

    int r, g, b;
    int i;

    if (name[0] == '#' && strlen((char *) name) == 7)
    {
        /* Name is in "#rrggbb" format */
        r = hex_digit(name[1]) * 16 + hex_digit(name[2]);
        g = hex_digit(name[3]) * 16 + hex_digit(name[4]);
        b = hex_digit(name[5]) * 16 + hex_digit(name[6]);
        if (r < 0 || g < 0 || b < 0)
            return INVALCOLOR;
        return RGB(r, g, b);
    }
    else
    {
        /* Check if the name is one of the colors we know */
        for (i = 0; i < sizeof(table) / sizeof(table[0]); i++)
            if (STRICMP(name, table[i].name) == 0)
            return table[i].color;
    }

    return INVALCOLOR;
}

void gui_mch_set_fg_color(guicolor_T color)
{
    NSLog(@"gui_mch_set_fg_color: #%02x%02x%02x", 
          Red(color), Green(color), Blue(color));
    fg_color = color;
}

void gui_mch_set_bg_color(guicolor_T color)
{
    NSLog(@"gui_mch_set_bg_color: #%02x%02x%02x", 
          Red(color), Green(color), Blue(color));
    bg_color = color;
}

void gui_mch_set_sp_color(guicolor_T color)
{
    NSLog(@"gui_mch_set_sp_color: #%02x%02x%02x", 
          Red(color), Green(color), Blue(color));

    sp_color = color;
}

/* Color related }}} */

/* Drawing related {{{ */

void gui_mch_flush()
{
    fprintf(stderr, "gui_mch_flush\n");
}

void gui_mch_invert_rectangle(int r, int c, int nr, int nc)
{
    fprintf(stderr, "gui_mch_invert_rectangle\n");
    
    dirty_all_rows();
    
    request_invert_rect(r, c, nr, nc);
    redraw();
}

void gui_mch_flash(int msec)
{
    fprintf(stderr, "gui_mch_flash\n");

    dirty_all_rows();
    
    request_flash();
    redraw();
}

void gui_mch_clear_all()
{
    fprintf(stderr, "gui_mch_clear_all\n");
    
    dirty_all_rows();
    
    gui_mch_set_bg_color(gui.back_pixel);
    request_clear_all(bg_color);
    redraw();
}

void gui_mch_clear_block(int row1, int col1, int row2, int col2)
{
    int i;
    
    fprintf(stderr, "gui_mch_clear_block\n");
    
    for (i = row1; i <= row2; i++)
        dirty_row(i);

    gui_mch_set_bg_color(gui.back_pixel);
    request_clear_block(row1, col1, row2, col2, bg_color);
    redraw();
}

void gui_mch_delete_lines(int row, int num_lines)
{
    fprintf(stderr, "gui_mch_delete_lines\n");
    
    request_delete_lines(row, num_lines);
    redraw();
}

void gui_mch_insert_lines(int row, int num_lines)
{
    fprintf(stderr, "gui_mch_insert_lines\n");
    
    request_insert_lines(row, num_lines);
    redraw();
}

void gui_mch_draw_hollow_cursor(guicolor_T color)
{
}

void gui_mch_draw_part_cursor(int w, int h, guicolor_T color)
{
}

void gui_mch_draw_string(int row, int col, char_u *s, int len, int flags)
{
    fprintf(stderr, "gui_mch_draw_string: (%d, %d, %d)\n", row, col, len);

    request_draw_string(row, col, s, len, flags);
    redraw();
}

/* Drawing related }}} */

/* Clipboard related {{{ */

void clip_mch_lose_selection(VimClipboard *cbd)
{
}

int clip_mch_own_selection(VimClipboard *cbd)
{
    return OK;
}

void clip_mch_set_selection(VimClipboard *cbd)
{
}

void clip_mch_request_selection(VimClipboard *cbd)
{
}

/* Clipboard related }}} */

/* Scrollbar related {{{ */

void gui_mch_create_scrollbar(scrollbar_T *sb, int orient)
{
}

void gui_mch_destroy_scrollbar(scrollbar_T *sb)
{
}

void gui_mch_enable_scrollbar(scrollbar_T *sb, int flag)
{
    fprintf(stderr, "enable scrollbar: %d\n", flag);
}

void gui_mch_set_scrollbar_pos(
    scrollbar_T *sb,
    int x,
    int y,
    int w,
    int h)
{
    fprintf(stderr, "set scrollbar pos: (%d, %d, %d, %d)\n", 
            x, y, w, h);
}

void gui_mch_set_scrollbar_thumb(
    scrollbar_T *sb,
    long val,
    long size,
    long max)
{
}

/* Scrollbar related }}} */

/* Mouse related {{{ */

void gui_mch_getmouse(int *x, int *y)
{
}

void gui_mch_setmouse(int x, int y)
{
}

/* Mouse related }}} */

/* Cursor blinking stuff {{{ */
    
void gui_mch_set_blinking(long wait, long on, long off)
{
}

void gui_mch_stop_blink()
{
}

void gui_mch_start_blink()
{
}

/* Cursor blinking stuff }}} */

/* Private Functions {{{1 */

int hex_digit(int c)
{
    if (isdigit(c))
        return c - '0';
    
    c = TOLOWER_ASC(c);
    
    if (c >= 'a' && c <= 'f')
        return c - 'a' + 10;
    
    return -1000;
}

NSColor *NSColorFromGuiColor(guicolor_T color)
{
    float red, green, blue;

    red   = (float) Red(color) / (float) 0xFF;
    green = (float) Green(color) / (float) 0xFF;
    blue  = (float) Blue(color) / (float) 0xFF;

    return [NSColor colorWithDeviceRed:red
                                 green:green
                                  blue:blue
                                 alpha:1.0];
}

/* Application Related Utilities {{{2 */

@implementation VimAppDelegate

- (void) applicationDidFinishLaunching:(NSNotification *)aNotification
{
    create_window(800, 600);
}

- (void) oneShotTimer:(NSTimer *)timer
{
    NSLog(@"Timer fired");
    [NSApp stop:self];
}

@end

/* Application Related Utilities 2}}} */

/* Menu Related Utilities {{{2 */

void set_application_menu()
{
    NSMenu *appleMenu, *services;
    NSMenuItem *menuItem;
    NSString *title;
    NSString *appName;
    
    appName = @"Vim";
    appleMenu = [[NSMenu alloc] initWithTitle:@""];

    /* Add menu items */
    title = [@"About " stringByAppendingString:appName];
    [appleMenu addItemWithTitle:title 
                         action:@selector(orderFrontStandardAboutPanel:) 
                  keyEquivalent:@""];

    [appleMenu addItem:[NSMenuItem separatorItem]];

    // Services Menu
    services = [[[NSMenu alloc] init] autorelease];
    [appleMenu addItemWithTitle:@"Services" 
                         action:nil
                  keyEquivalent:@""];
    [appleMenu setSubmenu: services forItem: [appleMenu itemWithTitle: @"Services"]];

    // Hide AppName
    title = [@"Hide " stringByAppendingString:appName];
    [appleMenu addItemWithTitle:title action:@selector(hide:) keyEquivalent:@"h"];

    // Hide Others
    menuItem = (NSMenuItem *)[appleMenu addItemWithTitle:@"Hide Others" 
                                                  action:@selector(hideOtherApplications:) 
                                           keyEquivalent:@"h"];
    [menuItem setKeyEquivalentModifierMask:(NSAlternateKeyMask|NSCommandKeyMask)];

    // Show All
    [appleMenu addItemWithTitle:@"Show All" 
                         action:@selector(unhideAllApplications:) 
                  keyEquivalent:@""];

    [appleMenu addItem:[NSMenuItem separatorItem]];

    // Quit AppName
    title = [@"Quit " stringByAppendingString:appName];
    [appleMenu addItemWithTitle:title 
                         action:@selector(terminate:) 
                  keyEquivalent:@"q"];

    /* Put menu into the menubar */
    menuItem = [[NSMenuItem alloc] initWithTitle:@"" 
                                          action:nil 
                                   keyEquivalent:@""];
    [menuItem setSubmenu: appleMenu];
    [[NSApp mainMenu] addItem: menuItem];

    /* Tell the application object that this is now the application menu */
    [NSApp setAppleMenu: appleMenu];
    [NSApp setServicesMenu: services];

    /* Finally give up our references to the objects */
    [appleMenu release];
    [menuItem release];
}

/* Menu Related Utilities 2}}} */

/* Window related Utilities {{{2 */

int create_window(int width, int height)
{
    NSWindow    *window;
    NSRect       contentRect;
    unsigned int style;

    contentRect = NSMakeRect(0, 0, width, height);
    style = NSTitledWindowMask | 
    NSMiniaturizableWindowMask | 
          NSClosableWindowMask | 
         NSResizableWindowMask;

    window = [[NSWindow alloc] initWithContentRect:contentRect 
                                         styleMask:style
                                           backing:NSBackingStoreBuffered 
                                             defer:NO];
    [window center];
    [window setViewsNeedDisplay:NO];
    [window setTitle:@"gVIM on Macintosh"];
    [window makeKeyAndOrderFront:nil];

    content_view = [[VimTextView alloc] initWithFrame:contentRect];
    [window setContentView:content_view];
    [window makeFirstResponder:content_view];

    return OK;
}

/* Window related Utilities 2}}} */

/* View related Utilities 2{{{ */

@implementation VimTextView

- (BOOL) hasMarkedText
{
    return NO;
}

- (NSRange) markedRange
{
    return NSMakeRange(NSNotFound, 0);
}

- (NSRange) selectedRange
{
    return NSMakeRange(NSNotFound, 0);
}

- (void) setMarkedText:(id)aString selectedRange:(NSRange)selRange
{
    NSLog(@"setMarkedText: %@ (%u, %u)", aString, 
          selRange.location, selRange.length);
}

- (void) unmarkText
{
    NSLog(@"unmarkText");
}

- (NSArray *) validAttributesForMarkedText
{
    return nil;
}

- (NSAttributedString *) attributedSubstringFromRange:(NSRange)theRange
{
    return nil;
}

- (void) insertText:(id)aString
{
    NSLog(@"insertText: %@", aString);
}

- (unsigned int) characterIndexForPoint:(NSPoint)thePoint
{
    return NSNotFound;
}

- (NSRect) firstRectForCharacterRange:(NSRange)theRange
{
    return NSMakeRect(0, 0, 0, 0);
}

- (long) conversationIdentifier
{
    return (long) self;
}

- (void) doCommandBySelector:(SEL)aSelector
{
    
}

- (void) drawRect:(NSRect)rect
{
    int i;
    drawing_request *req;
    
    for (i = 0; i < req_index; i++)
    {
        req = req_pool + i;

        switch (req->type)
        {
        case REQ_NONE:
            fprintf(stderr, "REQ_NONE\n");
            break;

        case REQ_CLEAR_BLOCK:
            [NSColorFromGuiColor(req->u.block.color) set];
            fprintf(stderr, "REQ_CLEAR_BLOCK\n");
            break;

        case REQ_CLEAR_ALL:
            fprintf(stderr, "REQ_CLEAR_ALL\n");
        
            [NSColorFromGuiColor(req->u.color) set];
            NSRectFill(rect);
            break;

        case REQ_INVERT_RECT:
            fprintf(stderr, "REQ_INVERT_RECT\n");
            break;

        case REQ_FLASH: 
            fprintf(stderr, "REQ_FLASH\n");
            break;

        case REQ_DRAW_STRING:
            fprintf(stderr, "REQ_DRAW_STRING\n");
            break;

        case REQ_DELETE_LINES: 
            fprintf(stderr, "REQ_DELETE_LINES\n");
            break;

        case REQ_INSERT_LINES:
            fprintf(stderr, "REQ_INSERT_LINES\n");
            break;
        }
    }
    
    request_clean_up();
}

- (BOOL)acceptsFirstResponder
{
    return YES;
}

#define INLINE_KEY_BUFFER_SIZE                      256
#define add_to_key_buffer(buf, len, k1, k2, k3)     { buf[len++] = k1; buf[len++] = k2; buf[len++] = k3; }

- (void) keyDown:(NSEvent *)event
{
    UniChar         modified_char, original_char;
    unsigned int    mac_modifiers, vim_modifiers;
    char_u          result[INLINE_KEY_BUFFER_SIZE];
    int             len = 0;
    int             vim_key_char;

    /* get key code and modifier flags from event */
    mac_modifiers  = [event modifierFlags];
    
    /* convert NS* style modifier flags to vim style */
    vim_modifiers = modifiers_to_vim(mac_modifiers);
    
    NSLog(@"keyDown: characters = %d\n", [[event characters] length]);

    if ([[event characters] length] == 1)
    { 
        modified_char = [[event characters] characterAtIndex: 0];
        original_char = [[event charactersIgnoringModifiers] characterAtIndex: 0];

        fprintf(stderr, "%04X\n", original_char);    

        /* Intercept CMD-. and CTRL-c */
        if (((vim_modifiers & MOD_MASK_CTRL) && original_char == 'c') ||
            ((vim_modifiers & MOD_MASK_CMD)  && original_char == '.'))
        {
            got_int = TRUE;
            
            app_is_running = FALSE;
            return;
        }
        
        /* if modified character != original character, */
        if (modified_char != original_char)
        {
            vim_modifiers &= ~MOD_MASK_SHIFT;
            vim_modifiers &= ~MOD_MASK_ALT;
        }

        if (vim_modifiers)
            add_to_key_buffer(result, len, CSI, KS_MODIFIER, vim_modifiers);
    
        vim_key_char = function_key_to_vim(original_char, vim_modifiers);
        if (IS_SPECIAL(vim_key_char))
        {
            add_to_key_buffer(result, len, CSI, 
                              K_SECOND(vim_key_char), 
                              K_THIRD(vim_key_char));
                  
            add_to_input_buf(result, len);
            
            app_is_running = FALSE;
            fprintf(stderr, "app_is_running = %s\n", 
                    app_is_running == TRUE ? "TRUE" : "FALSE");    
            return;
        }
    }
    
    [self interpretKeyEvents: [NSArray arrayWithObject: event]];
}

@end

/* View related Utilities 2}}} */

/* Drawing related Utilities {{{2 */

void redraw()
{
    [content_view setNeedsDisplay:YES];
}

void dirty_all_rows()
{
    int i;

    for (i = 0; i < gui.num_rows; i++)
        row_to_redraw[i] = 1;
}

void dirty_row(int row)
{
    if (row < gui.num_rows && row >= 0)
        row_to_redraw[row] = 1;
}

void clean_all_rows()
{
    int i;

    for (i = 0; i < gui.num_rows; i++)
        row_to_redraw[i] = 0;
}

void clean_row(int row)
{
    if (row < gui.num_rows && row >= 0)
        row_to_redraw[row] = 0;
}

/* Drawing related Utilities 2}}} */

/* Keyboard Related Utilities 2{{{ */

int function_key_to_vim(UniChar key_char, unsigned int vim_modifiers)
{
    int i;

    for (i = 0; function_key_mapping[i].function_key != 0; i++)
        if (key_char == function_key_mapping[i].function_key)
            return simplify_key(function_key_mapping[i].vim_key,
                                (int *) &vim_modifiers);

    return 0;
}

unsigned int modifiers_to_vim(unsigned int mac_modifiers)
{
    unsigned int vim_modifiers = 0;
        
    if (mac_modifiers & NSShiftKeyMask)
        vim_modifiers |= MOD_MASK_SHIFT;
    if (mac_modifiers & NSControlKeyMask)
        vim_modifiers |= MOD_MASK_CTRL;
    if (mac_modifiers & NSAlternateKeyMask)
        vim_modifiers |= MOD_MASK_ALT;
    if (mac_modifiers & NSCommandKeyMask)
        vim_modifiers |= MOD_MASK_CMD;
        
    return vim_modifiers;
}

/* Keyboard Related Utilities 2}}} */

/* Drawing request related Utilities {{{2 */

void request_clean_up()
{
    req_index = 0;
}

drawing_request *request_new(enum drawing_request_type type)
{
    req_pool[req_index].type = type;
    req_index++;

    return req_pool + req_index - 1;
}

void request_draw_string(int row, int col, char_u *s, int len, int flag)
{
    drawing_request *req;

    req = request_new(REQ_DRAW_STRING);

    req->u.str.row  = row;
    req->u.str.col  = row;
    req->u.str.s    = s;
    req->u.str.len  = len;
    req->u.str.flag = flag;
}

void request_clear_all(guicolor_T color)
{
    drawing_request *req;
    
    req = request_new(REQ_CLEAR_ALL);
    req->u.color = color;
}

void request_flash()
{
    
}

void request_clear_block(int row1, int col1, int row2, int col2, guicolor_T color)
{
    drawing_request *req;
    
    req = request_new(REQ_CLEAR_BLOCK);
    
    req->u.block.row1 = row1;
    req->u.block.col1 = col1;
    req->u.block.row2 = row2;
    req->u.block.col2 = col2;
    req->u.block.color = color;
}

void request_invert_rect(int row, int col, int row_num, int col_num)
{
    drawing_request *req;
    
    req = request_new(REQ_CLEAR_BLOCK);
    
    req->u.rect.row = row;
    req->u.rect.col = col;
    req->u.rect.row_num = row_num;
    req->u.rect.col_num = col_num;
}

void request_insert_lines(int row, int num_lines)
{
    drawing_request *req;
    
    req = request_new(REQ_INSERT_LINES);
    req->u.lines.row = row;
    req->u.lines.num = num_lines;
}

void request_delete_lines(int row, int num_lines)
{
    drawing_request *req;
    
    req = request_new(REQ_DELETE_LINES);
    req->u.lines.row = row;
    req->u.lines.num = num_lines;
}

/* Drawing request related Utilities 2}}} */

/* Private Functions 1}}} */
